package net.portly.cst;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import net.portly.cst.flex.FlexAssetAttribute;
import net.portly.cst.flex.FlexFamily;
import net.portly.cst.flex.FlexItemType;
import net.portly.cst.loader.ATDLoader;
import net.portly.cst.loader.SimpleLoader;
import net.portly.cst.loader.StructuralAssetLoader;
import net.portly.cst.page.PageAssetsContainer;
import net.portly.cst.tools.ArgsManager;

import org.w3c.dom.Document;
import org.xml.sax.SAXException;

/**
 * An {@link AssetModel} will - build a list of Flex Families, with
 * all their associated FlexTypes - then locate all dependent assets for those
 * types
 * 
 * @author cs76
 * 
 */
public class AssetModel implements java.io.Serializable {

	private static final long serialVersionUID = 1L;

	private String sep = System.getProperty("file.separator");

	private List<AssetTypeDescriptor> listOfAssetTypeDescriptors = new ArrayList<AssetTypeDescriptor>();

	private String where = "ASSET_TYPE";
	private StructuralAssetGraph structuralAssetMap = new StructuralAssetGraph();

	private List<FlexFamily> flexFamilies = null; 
	private PageAssetsContainer pageAssetsContainer = null;
	private ArgsManager argsManager = null;
	
	public AssetModel(ArgsManager argsManager) {
		this.argsManager = argsManager;
		listOfAssetTypeDescriptors = findAssetTypeDescriptors();
	}

	/**
	 * Given a set of AssetTypeDescriptors, we create a set of FlexFamilies. For
	 * each Flex Family, we then look for its dependent assets
	 * 
	 * @return a {@link java.util.List} of {@link net.portly.cst.flex.FlexFamily}
	 *         members
	 */
	public void build() {
		ATDConsolidationActivity activity = new ATDConsolidationActivity(listOfAssetTypeDescriptors, argsManager);
		flexFamilies = activity.consolidateAssetTypeDescriptorsForFlex();
		pageAssetsContainer = activity.consolidateAssetTypeDescriptorsForPage();
		
		for (FlexFamily flexFamily : flexFamilies) {
			buildDependentStructuralAssets(flexFamily, FlexItemType.ATTRIBUTE);
			buildDependentStructuralAssets(flexFamily, FlexItemType.FILTER);
			buildDependentStructuralAssets(flexFamily, FlexItemType.PARENTDEF);
			buildDependentStructuralAssets(flexFamily, FlexItemType.FLEXDEF);
		}

		buildDependentStructuralAssets(pageAssetsContainer, FlexItemType.PAGEATTR);
		buildDependentStructuralAssets(pageAssetsContainer, FlexItemType.PAGEDEF);
		buildDependentStructuralAssets(pageAssetsContainer, FlexItemType.PAGEFILTER);

		// * Now tie up all FlexAttrs using our asset instance
		Map<String, StructuralAssetInstance> assets = structuralAssetMap.getAssetGraphByIds();

		for (Iterator<StructuralAssetInstance> iterator = assets.values().iterator(); iterator.hasNext();) {
			StructuralAssetInstance instance = iterator.next();

			if (instance.getFlexAssetAttributes() != null) {
				for (FlexAssetAttribute attr : instance.getFlexAssetAttributes()) {
					attr.setStructuralAssetInstance(structuralAssetMap.getAssetById(attr.getAssetIdType().getId()));
				}
			}
		}
	}

	/**
	 * Use the CSDT structure to locate structural assets that are of a specific
	 * type and add them to a FlexFamily. They are also added to the
	 * structuralAsseGraph which is used to manage unsatisfied dependencies
	 * prior to returning
	 * 
	 * @param metaAssetContainer
	 * @param whatAreWeBuilding
	 */
	private void buildDependentStructuralAssets(MetaAssetContainer metaAssetContainer, FlexItemType whatAreWeBuilding) {
		AssetTypeDescriptor assetTypeWeAreChecking = metaAssetContainer.getAssetTypeDescriptorForFlexItem(whatAreWeBuilding);

		if (assetTypeWeAreChecking != null && assetTypeWeAreChecking.getAssettype() != null) {
			String location = this.argsManager.getRootDirectory() + sep + "src" + sep + "_metadata" + sep + "ASSET" + sep + assetTypeWeAreChecking.getAssettype() + sep;
			List<CSTDocument> docs = new ArrayList<CSTDocument>();

			// * Build a list of all the XML documents that match our asset type
			iterateDirectory(location, docs);

			// * Once we have the docs, we need to convert them to instances
			List<StructuralAssetInstance> listOfStructuralAssetsWeFound = buildStructuralAssetInstances(docs);
			metaAssetContainer.addInstancesListToStructuralAssetMap(whatAreWeBuilding, listOfStructuralAssetsWeFound);
			structuralAssetMap.addInstances(listOfStructuralAssetsWeFound);
		}
	}

	/**
	 * buildAssetTypes - for a nominated asset type, find all the variants
	 * 
	 * @return
	 */
	private List<AssetTypeDescriptor> findAssetTypeDescriptors() {
		String location = argsManager.getRootDirectory() + sep + "src" + sep + "_metadata" + sep + where + sep;
		List<CSTDocument> docs = new ArrayList<CSTDocument>();

		iterateDirectory(location, docs);

		List<AssetTypeDescriptor> atdList = new ArrayList<AssetTypeDescriptor>();
		for (CSTDocument doc : docs) {
			ATDLoader assetTypeLoader = new ATDLoader();
			assetTypeLoader.loadAttributes(doc);
			atdList.add(assetTypeLoader.getATD());
		}
		return atdList;
	}

	/**
	 * Iterate the {@link CSTDocument} list and build a list of structuralAsset s
	 * @param docs
	 * @return
	 */
	private List<StructuralAssetInstance> buildStructuralAssetInstances(List<CSTDocument> docs) {
		List<StructuralAssetInstance> structuralAssets = new ArrayList<StructuralAssetInstance>();

		// * For each document, create an object representation
		for (CSTDocument doc : docs) {
			SimpleLoader assetLoader = new StructuralAssetLoader();
			assetLoader.loadAttributes(doc);
			StructuralAssetInstance structuralAsset = assetLoader.getStructuralAssetInstance();
			structuralAssets.add(structuralAsset);
		}
		return structuralAssets;
	}

	private List<CSTDocument> iterateDirectory(String dirName, List<CSTDocument> docs) {
		File dir = new File(dirName);

		File[] fList = dir.listFiles();
		if (fList != null) {
			for (File file : fList) {

				// System.out.println("Checking " + file.getName());

				if (file.isFile() && file.getName().endsWith(".main.xml")) {
					try {
						docs.add(new CSTDocument(fileToDocument(file), file.getCanonicalPath()));
					} catch (IOException e) {
						e.printStackTrace();
					}
				} else if (file.isDirectory()) {
					iterateDirectory(file.getAbsolutePath(), docs);
				}
			}
		}

		return docs;
	}

	private Document fileToDocument(File locatedResource) {
		DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
		Document doc = null;
		try {
			DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
			doc = dBuilder.parse(locatedResource);
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		return doc;
	}

	public StructuralAssetGraph getStructuralAssetGraph() {
		return structuralAssetMap;
	}

	public List<AssetTypeDescriptor> getListOfAssetTypeDescriptors() {
		return listOfAssetTypeDescriptors;
	}

	public List<FlexFamily> getFlexFamilies() {
		return flexFamilies;
	}

	public PageAssetsContainer getPageAssetsContainer() {
		return pageAssetsContainer;
	}
}
